'''
    
    Base classes for KMap object (input algos)
    
'''

class AbstractKMap(object):
    @classmethod
    def get_default_config(cls):
        '''
        Pure Virtual, must be implemented by subclass
        to return an instance of a AbstractKMapConfig subclass.
        
        Subclass should create a config for each call unless this
        map intends to share the config with all of its instances.
        (I.e the config is a singleton, and this does not feel like
        a config...)
        
        '''
        raise NotImplementedError
    
    def __init__(self, config=None):
        '''
        Creates the algo instance using config.
        If config is not given, self.get_default_config() will be used.
        (Subclasses must implement it.)
        
        '''
        object.__init__(self)
        if config is None:
            config = self.get_default_config()
        self.set_config(config)
        
        # protected attributes to store KMap states:
        
        self._keychain = []     # current pressed keys
        self._applied = False   # process on next key release?
        self._cap_next = False  # next is uppercase?
        self._caps_lock = False # all is uppercase ?
        
        # protected attributes to store keychain converter:
        
        self._keychain_map = {}       # default keychain map
        self._next_keychain_map = {}  # keychain map override or None
        
    def set_config(self, config):
        '''
        Changes the KMap config.
        Subclasses should override this and call the parent
        implementation.
        '''
        self._config = config
    
    def set_one_shot_keychain_map(self, keychain_map):
        '''
        This is used to change the keychain map to use for
        next get_result call.
        See also set_default_keychain_map()
        '''
        self._next_keychain_map = keychain_map
        
    def set_default_keychain_map(self, keychain_map):
        '''
        This changes the default keychain map, i.e. the
        one used when no one shot map has been set.
        '''
        self._keychain_map = keychain_map
    
    def get_keychain(self):
        '''
        Returns a copy of the current keychain.
        
        Default behavior is to return a tuple and thus use
        key press order to find out the keychain result.
        
        Subclasses may override this and/or keychain_eq to
        modify this policy.
        When doing so, you must start by calling the base 
        implementation and optionaly modify it before returning it.
        '''
        return list(self._keychain)
    
    @classmethod
    def keychain_eq(cls, kca, kcb):
        '''
        Compares the two given keychains and returns
        True if they must be considered equal.
        
        This is called to find a key in the keychain_map when
        requesting the result of a keychain.
        
        You may subclass to change the meaning of the keypress
        order or the bad keys pressed etc...
        
        '''
        return kca == kcb
        
    def get_result(self):
        '''
        Returns the result of the current keychain.
        The returned value may be one of:
            - a single char
            - a callable object with no arguments returning a string
            - an keycode int
            - a 2d tuple like: (keycode, (shift_state, ctrl_tate, alt_state))
            - None (meaning 'do nothing')
        
        This is not to be overriden, subclasses should implement
        the _get_result() method instead.
        '''
        if self.BADKEY in self._keychain:
            # Do nothing if we have pressed some bad key
            return None
            
        keychain = self.get_keychain()
        
        if self._next_keychain_map is not None:
            keychain_map = self._next_keychain_map
            self._next_keychain_map = None
        else:
            keychain_map = self._keychain_map
            
        result = None
        for k, v in keychain_map.iteritems():
            if self.keychain_eq( k, keychain):
                result = v
                break
        
        if callable(result):
            result = result()
        
        if self._cap_next:
            if isinstance(result, basestring):
                result = result.upper()
            self._cap_next = False
        
        return result
    
    def key_pressed(self, keycode):
        self._applied = False
        kc = self.config.convert_keycode(keycode)
        if kc is None:
            return
        if kc not in self._keychain:
            self._keychain.append(kc)
            #print '  >>', self._keychain_str()
    
    def key_released(self, keycode):
        kc = self.config.convert_keycode(keycode)
        
        ret = None
        if not self._applied:
            ret = self.get_result()
        
        if kc in self._keychain:
            self._keychain.remove(kc)
        #print '  >>', self._keychain_str()
        
        return ret
    
class AbstractKMapConfig(object):
    KBAD = None # Represent a not usable keycode.
    
    def __init__(self):
        '''
        You will want to subclass this and use it in the 
        get_default_config class method of your AbstractKMap
        subclasses.
        
        The config object should store everything that could
        modify the behavior of you kmap algo like:
            - specific keycodes to use
            - keycode names
            - values for the kmap properties
        
        The keys parameter is used to name the keys used by
        this config.
        Each of those key will be accessible as an instance
        attribute
        Use the keys() method to get all keys.
        '''
        object.__init__(self, **keys)
        self._keys = keys
        for k, v in keys:
            setattr( self, k, v )
    
    def keys(self):
        '''
        Return a dict like {keyname: keycode}
        (The keyword arguments given in the constructor)
        '''
        return self._keys
    
    def convert_keycode(self, keycode):
        '''
        Before storing a keycode, the KMap calls this converter.
        Default implementation is to return KBAD when the
        key is not used by the config.
        Subclasses could choose to return None, meaning that the 
        keypress should be ignored.
        '''
        if keycode not in self.keys().values():
            return self.BADKEY
        return keycode
        
    def key_name(self, keycode):
        '''
        Returns the string representation of the given
        keycode.
        '''
        if keycode == self.KBAD:
            return 'BAD_KEY'
        return chr(keycode)
    
    def keychain_str(self, keychain):
        '''
        Returns a string representing a list of keycodes.
        Uses self.key_name() on each keycode a joins it with
        comas.
        '''
        return ', '.join([ self._key_name(kc) for kc in keychain ])

